<!DOCTYPE html>
<html lang="en" style="">
<head>
<!--shared:custom-meta-->
<base href="">
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script>
let DEBUG = false
DEBUG = true /*debug*/
</script>
<style>
/*vfs[]:/css/ui-highlight.css*/
/*vfs[]:/css/ui.css*/
/*file:custom.css */

/* @tailwindcss/typography */
.prose {color: inherit;max-width: 65ch }
.prose :where([class~="lead"]):not(:where([class~="not-prose"] *)) {color: var(--tw-prose-lead);font-size: 1.25em;line-height: 1.6;margin-top: 1.2em;margin-bottom: 1.2em }
.prose :where(a):not(:where([class~="not-prose"] *)) {color: inherit;text-decoration: underline;font-weight: 500 }
.prose :where(a):not(:where([class~="not-prose"] *)):hover {opacity: .8;color: #4b5563 }
.prose :where(strong):not(:where([class~="not-prose"] *)) {color: inherit;font-weight: 600 }
.prose :where(ol):not(:where([class~="not-prose"] *)) {list-style-type: decimal;padding-left: 1.625em }
.prose :where(ol[type="A"]):not(:where([class~="not-prose"] *)) {list-style-type: upper-alpha }
.prose :where(ol[type="a"]):not(:where([class~="not-prose"] *)) {list-style-type: lower-alpha }
.prose :where(ol[type="A"]):not(:where([class~="not-prose"] *)) {list-style-type: upper-alpha }
.prose :where(ol[type="a"]):not(:where([class~="not-prose"] *)) {list-style-type: lower-alpha }
.prose :where(ol[type="I"]):not(:where([class~="not-prose"] *)) {list-style-type: upper-roman }
.prose :where(ol[type="i"]):not(:where([class~="not-prose"] *)) {list-style-type: lower-roman }
.prose :where(ol[type="I"]):not(:where([class~="not-prose"] *)) {list-style-type: upper-roman }
.prose :where(ol[type="i"]):not(:where([class~="not-prose"] *)) {list-style-type: lower-roman }
.prose :where(ol[type="1"]):not(:where([class~="not-prose"] *)) {list-style-type: decimal }
.prose :where(ul):not(:where([class~="not-prose"] *)) {list-style-type: disc;padding-left: 1.625em }
.prose :where(ol > li):not(:where([class~="not-prose"] *))::marker {font-weight: 400;color: var(--tw-prose-counters) }
.prose :where(ul > li):not(:where([class~="not-prose"] *))::marker {color: var(--tw-prose-bullets) }
.prose :where(hr):not(:where([class~="not-prose"] *)) {border-color: var(--tw-prose-hr);border-top-width: 1px;margin-top:1em;margin-bottom:1em }
.prose :where(blockquote):not(:where([class~="not-prose"] *)) {font-weight: 500;color: var(--tw-prose-quotes);border-left-width: .25rem;border-left-color: var(--tw-prose-quote-borders);quotes: "\201c""\201d""\2018""\2019";margin-top: 1.6em;margin-bottom: 1.6em;padding-left: 1em }
.prose :where(blockquote p:first-of-type):not(:where([class~="not-prose"] *)):before {content: open-quote }
.prose :where(blockquote p:last-of-type):not(:where([class~="not-prose"] *)):after {content: close-quote }
.prose :where(h1):not(:where([class~="not-prose"] *)) {color: inherit;font-weight: 800;font-size: 2.25em;margin-top: 0;margin-bottom: .8888889em;line-height: 1.1111111 }
.prose :where(h1 strong):not(:where([class~="not-prose"] *)) {font-weight: 900 }
.prose :where(h2):not(:where([class~="not-prose"] *)) {color: inherit;font-weight: 700;font-size: 1.5em;margin-top: 2em;margin-bottom: 1em;line-height: 1.3333333 }
.prose :where(h2 strong):not(:where([class~="not-prose"] *)) {font-weight: 800 }
.prose :where(h3):not(:where([class~="not-prose"] *)) {color: inherit;font-weight: 600;font-size: 1.25em;margin-top: 1.6em;margin-bottom: .6em;line-height: 1.6 }
.prose :where(h3 strong):not(:where([class~="not-prose"] *)) {font-weight: 700 }
.prose :where(h4):not(:where([class~="not-prose"] *)) {color: inherit;font-weight: 600;margin-top: 1.5em;margin-bottom: .5em;line-height: 1.5 }
.prose :where(h4 strong):not(:where([class~="not-prose"] *)) {font-weight: 700 }
.prose :where(figure > *):not(:where([class~="not-prose"] *)) {margin-top: 0;margin-bottom: 0 }
.prose :where(figcaption):not(:where([class~="not-prose"] *)) {color: var(--tw-prose-captions);font-size: .875em;line-height: 1.4285714;margin-top: .8571429em }
.prose :where(code):not(:where([class~="not-prose"] *)) {color: #3b82f6;font-weight: 400;font-size: .875em;background-color: #eff6ff;border:1px solid #3b83f680;border-radius: .25rem;padding: .2rem .25rem .1rem .25rem}
.prose :where(code):not(:where([class~="not-prose"] *)):before {content: "" }
.prose :where(code):not(:where([class~="not-prose"] *)):after {content: "" }
.prose :where(a code):not(:where([class~="not-prose"] *)) {color: var(--tw-prose-links) }
.prose :where(pre code):not(:where([class~="not-prose"] *)) {background-color: transparent;border-width: 0;border-radius: 0;padding: 0;font-weight: inherit;color: inherit;font-size: inherit;font-family: inherit;line-height: inherit }
.prose :where(pre code):not(:where([class~="not-prose"] *)):before {content: none }
.prose :where(pre code):not(:where([class~="not-prose"] *)):after {content: none }
.prose :where(table):not(:where([class~="not-prose"] *)) {width: 100%;table-layout: auto;text-align: left;margin-top: 2em;margin-bottom: 2em;font-size: .875em;line-height: 1.7142857;background-color:#fff }
.prose :where(thead):not(:where([class~="not-prose"] *)) {border-bottom-width: 1px;border-bottom-color: var(--tw-prose-th-borders) }
.prose :where(thead th):not(:where([class~="not-prose"] *)) {color: var(--tw-prose-headings);font-weight: 600;vertical-align: bottom;padding-right: .5714286em;padding-bottom: .5714286em;padding-left: .5714286em }
.prose :where(tbody tr):not(:where([class~="not-prose"] *)) {border-bottom-width: 1px;border-bottom-color: var(--tw-prose-td-borders) }
.prose :where(tbody tr:last-child):not(:where([class~="not-prose"] *)) {border-bottom-width: 0 }
.prose :where(tbody td):not(:where([class~="not-prose"] *)) {vertical-align: baseline;padding: .5714286em }
.prose {--tw-prose-body: #374151;--tw-prose-headings: #111827;--tw-prose-lead: #4b5563;--tw-prose-links: #111827;--tw-prose-bold: #111827;--tw-prose-counters: #6b7280;--tw-prose-bullets: #d1d5db;--tw-prose-hr: #e5e7eb;--tw-prose-quotes: #111827;--tw-prose-quote-borders: #e5e7eb;--tw-prose-captions: #6b7280;--tw-prose-code: #111827;--tw-prose-pre-code: #e5e7eb;--tw-prose-pre-bg: #282c34;--tw-prose-th-borders: #d1d5db;--tw-prose-td-borders: #e5e7eb;--tw-prose-invert-body: #d1d5db;--tw-prose-invert-headings: #fff;--tw-prose-invert-lead: #9ca3af;--tw-prose-invert-links: #fff;--tw-prose-invert-bold: #fff;--tw-prose-invert-counters: #9ca3af;--tw-prose-invert-bullets: #4b5563;--tw-prose-invert-hr: #374151;--tw-prose-invert-quotes: #f3f4f6;--tw-prose-invert-quote-borders: #374151;--tw-prose-invert-captions: #9ca3af;--tw-prose-invert-code: #fff;--tw-prose-invert-pre-code: #d1d5db;--tw-prose-invert-pre-bg: rgb(0 0 0 / 50%);--tw-prose-invert-th-borders: #4b5563;--tw-prose-invert-td-borders: #374151;font-size: 1rem;line-height: 1.75 }
.prose :where(p):not(:where([class~="not-prose"] *)) {margin-top: 1.25em;margin-bottom: 1.25em }
.prose :where(img):not(:where([class~="not-prose"] *)) {margin-top: 2em;margin-bottom: 2em }
.prose :where(video):not(:where([class~="not-prose"] *)) {margin-top: 2em;margin-bottom: 2em }
.prose :where(figure):not(:where([class~="not-prose"] *)) {margin-top: 2em;margin-bottom: 2em }
.prose :where(h2 code):not(:where([class~="not-prose"] *)) {font-size: .875em }
.prose :where(h3 code):not(:where([class~="not-prose"] *)) {font-size: .9em }
.prose :where(li):not(:where([class~="not-prose"] *)) {margin-top: .5em;margin-bottom: .5em }
.prose :where(ol > li):not(:where([class~="not-prose"] *)) {padding-left: .375em }
.prose :where(ul > li):not(:where([class~="not-prose"] *)) {padding-left: .375em }
.prose>:where(ul > li p):not(:where([class~="not-prose"] *)) {margin-top: .75em;margin-bottom: .75em }
.prose>:where(ul > li > *:first-child):not(:where([class~="not-prose"] *)) {margin-top: 1.25em }
.prose>:where(ul > li > *:last-child):not(:where([class~="not-prose"] *)) {margin-bottom: 1.25em }
.prose>:where(ol > li > *:first-child):not(:where([class~="not-prose"] *)) {margin-top: 1.25em }
.prose>:where(ol > li > *:last-child):not(:where([class~="not-prose"] *)) {margin-bottom: 1.25em }
.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~="not-prose"] *)) {margin-top: .75em;margin-bottom: .75em }
.prose :where(hr + *):not(:where([class~="not-prose"] *)) {margin-top: 0 }
.prose :where(h2 + *):not(:where([class~="not-prose"] *)) {margin-top: 0 }
.prose :where(h3 + *):not(:where([class~="not-prose"] *)) {margin-top: 0 }
.prose :where(h4 + *):not(:where([class~="not-prose"] *)) {margin-top: 0 }
.prose>:where(:first-child):not(:where([class~="not-prose"] *)) {margin-top: 0 }
.prose>:where(:last-child):not(:where([class~="not-prose"] *)) {margin-bottom: 0 }
.prose :where(b):not(:where([class~="not-prose"] *)) {color: inherit }
.prose :where(em):not(:where([class~="not-prose"] *)) {color: inherit }
.prose-sm :where([class~="lead"]):not(:where([class~="not-prose"] *)),
.prose-sm :where(pre code):not(:where([class~="not-prose"] *))
{font-size: .875em;line-height: 1.2 }

</style>
<!--shared:custom-head-->
<script type="importmap">
{
    "imports": {
        "vue": "../js/vue.mjs",  
        "color.js": "../js/color.js",
        "chart.js": "../js/chart.js",
        "highlight.js": "../js/highlight.mjs",
        "@servicestack/vue": "../js/servicestack-vue.mjs",
        "@servicestack/client": "../js/servicestack-client.mjs",
        "highlight.js": "../js/highlight.mjs",
        "core": "../js/core.mjs",
        "app": "./lib/app.mjs",
        "dtos": "./lib/dtos.mjs",
        "charts": "./lib/charts.mjs",
        "marked": "/js/marked.mjs"
    }
}
</script>
</head>
<body class="loading noauth">
<!--shared:custom-body-->

<div id="app"></div>

<template id="app-template">
<div>
    <div v-if="store.auth && store.isAdmin" class="">
        <sidebar v-if="showSidebar" @hide="showSidebar=false"></sidebar>
        <div class="md:pl-64 flex flex-col flex-1 overflow-auto h-screen">
            <div class="flex flex-1">
                <div class="sticky top-0 z-10 md:hidden pl-1 pt-1 sm:pl-3 sm:pt-3 bg-white">
                    <button type="button" @click="showSidebar=true"
                            class="-ml-0.5 -mt-0.5 h-12 w-12 inline-flex items-center justify-center rounded-md text-gray-500 hover:text-gray-900 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500">
                        <span class="sr-only">Open sidebar</span>
                        <!---: Heroicon name: outline/menu -->
                        <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
                        </svg>
                    </button>
                </div>
                <main class="flex-1 overflow-auto flex flex-col">
                    <div v-if="server.ui.admin.pages.find(x => x.page === routes.admin)" class="flex-1 flex flex-col">
                        <component :is="app.component(server.ui.admin.pages.find(x => x.page === routes.admin).component)"></component>
                    </div>
                    <div v-else class="py-6">
                        <div class="mx-auto px-4 sm:px-6 md:px-8">
                            <h1 class="text-2xl font-semibold text-gray-900">{{store.link?.label}}</h1>
                        </div>
                        <div class="mx-auto px-4 sm:px-6 md:px-8">
                            <div class="py-4">
                                <dashboard v-if="(routes.admin ?? '') === ''"></dashboard>
                                <analytics v-else-if="routes.admin === 'analytics'"></analytics>
                                <identity-users v-else-if="routes.admin === 'users' && server?.plugins.auth?.identityAuth"></identity-users>
                                <users v-else-if="routes.admin === 'users'"></users>
                                <identity-roles v-else-if="routes.admin === 'roles'"></identity-roles>
                                <api-keys v-else-if="routes.admin === 'apikeys'"></api-keys>
                                <logging v-else-if="routes.admin === 'logging'"></logging>
                                <profiling v-else-if="routes.admin === 'profiling'"></profiling>
                                <commands v-else-if="routes.admin === 'commands'"></commands>
                                <background-jobs v-else-if="routes.admin === 'backgroundjobs'"></background-jobs>
                                <validation v-else-if="routes.admin === 'validation'"></validation>
                                <redis v-else-if="routes.admin === 'redis'"></redis>
                                <database v-else-if="routes.admin === 'database'"></database>
                                <div v-else>
                                    <section v-if="routes.admin === 'chat'">
                                        <div class="p-4 max-w-3xl">
                                            <alert type="info">Admin Chat UI is not enabled</alert>
                                            <div class="my-4">
                                                <div>
                                                    <p>
                                                        The <b>ChatFeature</b> plugin needs to be configured with your App
                                                        <a href="https://docs.servicestack.net/ai-chat-api" class="ml-2 whitespace-nowrap font-medium text-blue-700 hover:text-blue-600" target="_blank">
                                                            Learn more <span aria-hidden="true">&rarr;</span>
                                                        </a>
                                                    </p>
                                                </div>
                                            </div>
                                            <div>
                                                <p class="text-sm text-gray-700 my-2">Quick start:</p>
                                                <copy-line text="npx add-in chat"></copy-line>
                                            </div>
                                        </div>
                                    </section>
                                    <alert v-else type="info">
                                        This Admin UI feature is not currently installed or enabled in your ServiceStack application.
                                        <a href="https://docs.servicestack.net/admin-ui" target="_blank" class="whitespace-nowrap font-medium text-blue-700 hover:text-blue-600">
                                            Learn more <span aria-hidden="true">&rarr;</span>
                                        </a>
                                    </alert>
                                </div>
                            </div>
                        </div>
                    </div>
                </main>
            </div>
        </div>
    </div>
    <div v-else-if="store.auth && !store.isAdmin" class="w-full h-screen flex justify-center items-center">
        <div class="flex flex-col items-center">
            <alert><b>Admin</b> role required</alert>
            <div class="mt-4">
                <button type="button" @click="store.logout()"
                        class="flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
                    Logout
                </button>
            </div>
        </div>
    </div>
    <div v-else-if="server.plugins.auth">
        <sign-in title="Sign in to your account" :provider="routes.provider" @login="store.login($event)"></sign-in>
        <div v-if="server.plugins.apiKey" class="flex justify-center">
            <a class="p-1 inline-flex items-center border border-transparent hover:border-gray-400 hover:shadow-sm text-sm rounded bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
               aria-expanded="false" aria-haspopup="true" v-href="{ dialog:'apikey' }">
                <svg class="w-6 h-6 text-gray-600" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"><path d="M2 18v3c0 .6.4 1 1 1h4v-3h3v-3h2l1.4-1.4a6.5 6.5 0 1 0-4-4Z"/><circle cx="16.5" cy="7.5" r=".5" fill="currentColor"/></g></svg>
            </a>
        </div>
    </div>
    <api-key-dialog v-if="routes.dialog === 'apikey'" class="z-40" :title="server?.plugins?.apiKey?.label" @done="updatedApiKey"></api-key-dialog>
    <div v-if="apiKeyError" class="mt-4 flex justify-center">
        <alert type="error" class="w-96">{{apiKeyError}}</alert>
    </div>
</div>
</template>

<script>
function loadAuth(auth) {
    window.AUTH = !auth || auth.responseStatus ? null : auth
}
/*gateway:window.Server=MetadataApp({view:'locode',jsconfig:'eccn,inv:false'})?*/
</script>
<script src="../auth?callback=loadAuth&jsconfig=eccn"></script>

<script type="module">
import { useMetadata, useAuth } from "@servicestack/vue"
import { setFavIcon, setBodyClass } from "core"
import { humanify } from "@servicestack/client"

const { loadMetadata } = useMetadata()
const { signIn, toAuth } = useAuth()

globalThis.AUTH = toAuth(globalThis.AUTH)
loadMetadata()
signIn(globalThis.AUTH)
setBodyClass({ auth:globalThis.AUTH })

document.title = globalThis.Server.app.serviceName
setFavIcon(globalThis.Server.ui.brandIcon, globalThis.Server.app.baseUrl + '/metadata/svg/servicestack.datauri')
</script>

<script>
/*files:js/*.js */
</script>
<!--components:Brand,CopyIcon,SortableColumn,ApiKeyDialog-->
<!--module:components/*.mjs-->
<!--file:custom.html-->

<script type="module">
import { ref, computed, nextTick, inject, onMounted } from "vue"
import ServiceStackVue from "@servicestack/vue"
import { $1, $$, setQueryString } from "@servicestack/client"
import { app, client } from "app"
import { Authenticate } from "dtos"
import metadata from "./lib/metadata.mjs"
import { useUtils,useConfig,useMetadata } from "@servicestack/vue"
const { pushState } = useUtils()

window.client = client
const App = {
    template: $1('#app-template'),
    setup(props) {
        const store = inject('store')
        const routes = inject('routes')
        const server = inject('server')
        const apiKeyError = ref('')
        
        const showSidebar = ref(true)

        let sub = null
        let lastOp = null
        
        onMounted(() => {
            sub = app.events.subscribe('route:nav', args => {
                // need to refresh to update when navigating to the same op/page 
                if (args.op && args.op === lastOp) {
                    // console.log('route:nav', JSON.stringify(args))
                }
                lastOp = args.op
            })
        })
        
        async function updatedApiKey() {
            routes.to({ dialog:'' })
            const api = await client.api(new Authenticate())
            store.auth = window.AUTH = api.response
            apiKeyError.value = api.error
                ? `Not a valid 'Admin' API Key`
                : ''
        }
        
        return { app, store, routes, server, showSidebar, apiKeyError, updatedApiKey }
    }
}

Object.keys(app.Components).forEach(name => {
    ServiceStackVue.component(name, app.Components[name])
})

app.build(App).mount('#app')

app.start()

function configureMetadata(api) {
    function typeOf(name, namespace) {
        if (!api || !name) return null
        let type = api.types.find(x => x.name.toLowerCase() === name.toLowerCase() && (!namespace || x.namespace == namespace))
        if (type) return type
        let requestOp = apiOf(name)
        if (requestOp) return requestOp.request
        let responseOp = api.operations.find(x => x.response && (x.response.name.toLowerCase() === name.toLowerCase() && (!namespace || x.response.namespace == namespace)))
        if (responseOp) return responseOp.response
        return null
    }
    function apiOf(name) {
        if (!api) return null
        let requestOp = api.operations.find(x => x.request.name.toLowerCase() === name.toLowerCase())
        return requestOp
    }
    
    const {setConfig} = useConfig()
    const {Apis} = useMetadata()
    setConfig({
        apisResolver: (type, metaTypes) => {
            const apis = new Apis();
            metaTypes = api
            metaTypes?.operations.forEach(op => {
                if (op.dataModel?.name === type) {
                    apis.add(op)
                }
            })
            return apis
        },
        apiResolver: (name) => {
            return apiOf(name)
        },
        typeResolver: (name, namespace) => {
            return typeOf(name, namespace)
        }
    })
}
configureMetadata(metadata)
</script>
<script>
/*file:custom.js */
</script>

<!--shared:custom-end-->
</body>
</html>
